﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Spring.Objects.Factory.Config;
using Spring.Objects.Factory.Support;
using Strongshell.Recoil.Core.Composition.Parsing;
using Strongshell.Recoil.Core.Composition.Parsing.DependencyInjection;
using Strongshell.Recoil.Core.Composition.Workflow;

namespace Strongshell.Recoil.Core.Composition
{
    public abstract class WiringContainer : IObjectConfigurationRegistry
    {
        private readonly List<ObjectConfiguration> objectConfigurations = new List<ObjectConfiguration>();

        public IObjectNameFactory NameFactory { get; set; }

        public IObjectDefinitionFactory ObjectDefinitionFactory { get; set; }

        public List<ObjectConfiguration> ObjectConfigurations { get { return objectConfigurations; } }

        public IExpressionParserFactory ExpressionParserFactory { get; set; }

        /// <summary>
        /// Object configuration should be set up in the overriding method.
        /// </summary>
        public abstract void SetupContainer();

        /// <summary>
        /// Default constructor that uses the default implementations of object configuration facilities.
        /// </summary>
        protected WiringContainer()
        {
            NameFactory = new DefaultObjectNameFactory();
            ObjectDefinitionFactory = new DefaultObjectDefinitionFactory();
            ExpressionParserFactory = new DefaultExpressionParserFactory(this);
        }

        /// <summary>
        /// Creates a new object definition using a parameterless constructor.
        /// </summary>
        /// <typeparam name="TType">The type of the object.</typeparam>
        /// <returns>Fluent interface for configuring the object definition.</returns>
        protected IDefinitionFragment<TType> Define<TType>() where TType : class
        {
            var configuration = Register(typeof(TType), NameFactory.CreateName<TType>());

            return new DefinitionFragment<TType>(new FragmentContext(configuration, NameFactory, ExpressionParserFactory, this));
        }

        /// <summary>
        /// Creates a new object definition with a custom instantiation expression. Depending on the 
        /// expression, the object will be instantiated either using a contructor, static factory method or instance
        /// factory method. Objects can be embedded to any level, and using member initialization it is possible
        /// to wire properties as well.
        /// </summary>
        /// <typeparam name="TType">The type of the object.</typeparam>
        /// <param name="instantiatonExpression">The expression that defines the instantiation strategy.</param>
        /// <returns>Fluent interface for configuring the object definition.</returns>
        protected IDefinitionFragment<TType> Define<TType>(Expression<Func<TType>> instantiatonExpression) where TType : class
        {
            var configuration = Register(typeof(TType), NameFactory.CreateName<TType>());

            return DefineByExpression(instantiatonExpression, configuration);
        }

        /// <summary>
        /// Creates a new object definition using name of parent object definition.
        /// </summary>
        /// <typeparam name="TType">The type of the object.</typeparam>
        /// <param name="parentName">Parent object definition name</param>
        /// <returns>Fluent interface for configuring the object definition.</returns>
        protected IDefinitionFragment<TType> Define<TType>(string parentName) where TType : class
        {
            var configuration = Register(typeof(TType), NameFactory.CreateName<TType>(), parentName);

            return new DefinitionFragment<TType>(new FragmentContext(configuration, NameFactory, ExpressionParserFactory, this));
        }

        /// <summary>
        /// Creates a new object definition using parent object definition.
        /// </summary>
        /// <typeparam name="TType">The type of the object.</typeparam>
        /// <param name="parentDefinition">Parent object definition.</param>
        /// <returns>Fluent interface for configuring the object definition.</returns>
        protected IDefinitionFragment<TType> Define<TType>(INamedDefinitionFragment parentDefinition) where TType : class
        {
            return Define<TType>(parentDefinition.Name);
        }

        /// <summary>
        /// Creates a new object definition with a custom instantiation expression and using name of parent object definition.
        /// </summary>
        /// <typeparam name="TType">The type of the object.</typeparam>
        /// <param name="instantiatonExpression">The expression that defines the instantiation strategy.</param>
        /// <param name="parentName">Parent object definition name.</param>
        /// <returns>Fluent interface for configuring the object definition.</returns>
        protected IDefinitionFragment<TType> Define<TType>(Expression<Func<TType>> instantiatonExpression, string parentName) where TType : class
        {
            var configuration = Register(typeof(TType), NameFactory.CreateName<TType>(), parentName);

            return DefineByExpression(instantiatonExpression, configuration);
        }

        /// <summary>
        /// Creates a new object definition with a custom instantiation expression and using parent object definition.
        /// </summary>
        /// <typeparam name="TType">The type of the object.</typeparam>
        /// <param name="instantiatonExpression">The expression that defines the instantiation strategy.</param>
        /// <param name="parentDefinition">Parent object definition.</param>
        /// <returns>Fluent interface for configuring the object definition.</returns>
        protected IDefinitionFragment<TType> Define<TType>(Expression<Func<TType>> instantiatonExpression, INamedDefinitionFragment parentDefinition) where TType : class
        {
            return Define(instantiatonExpression, parentDefinition.Name);
        }

        /// <summary>
        /// Generic marker method for wiring dependencies. The expression parser will handle any occurence of
        /// this method as an intention to inject a dependency that is registered using the supplied generic type parameter.
        /// This method is not intended to be called and will throw a <see cref="NotSupportedException"/> if attempted.
        /// </summary>
        /// <typeparam name="TDependency">The type which should be wired. Usually it is an interface that has
        /// its implementation registered with the container.</typeparam>
        /// <returns>The method will throw a <see cref="NotSupportedException"/> when called.</returns>
        public static TDependency Wire<TDependency>()
        {
            throw new NotSupportedException("The Wire marker method is not intended to be called directy.");
        }

        /// <summary>
        /// A dependency wiring marker method that will indiciate an injection using the supplied reference name.
        /// The expression parser will handle any occurence of this method as an intention to inject a dependency that
        /// is registered using the supplied reference name.
        /// This method is not intended to be called and will throw a <see cref="NotSupportedException"/> if attempted.
        /// </summary>
        /// <typeparam name="TDependency">The type which should be wired. Usually it is an interface that has
        /// its implementation registered with the container.</typeparam>
        /// <param name="reference">The name of the object to inject.</param>
        /// <returns>The method will throw a <see cref="NotSupportedException"/> when called.</returns>
        public static TDependency Wire<TDependency>(string reference)
        {
            throw new NotSupportedException("The Wire marker method is not intended to be called directy.");
        }

        #region IObjectConfigurationRegistry Members

        ObjectConfiguration IObjectConfigurationRegistry.Register(Type type)
        {
            return Register(type, NameFactory.CreateName(type));
        }

        ObjectConfiguration IObjectConfigurationRegistry.RegisterUnique(Type type)
        {
            return Register(type, NameFactory.CreateUniqueName(type));
        }

        private ObjectConfiguration Register(Type type, string name)
        {
            var builder = ObjectDefinitionBuilder.RootObjectDefinition(ObjectDefinitionFactory, type);
            var container = new ObjectConfiguration(builder, name);
            ObjectConfigurations.Add(container);
            return container;
        }

        private ObjectConfiguration Register(Type type, string name, string parentName)
        {
            var builder = ObjectDefinitionBuilder.ChildObjectDefinition(ObjectDefinitionFactory, parentName);

            // Workaround to bypass ObjectDefinitionBuilder limitations.
            // Does not affect normal processing, because working as ObjectDefinitionBuilder itself
            builder.ObjectDefinition.ObjectType = type;

            var container = new ObjectConfiguration(builder, name);
            ObjectConfigurations.Add(container);
            return container;
        }

        private IDefinitionFragment<TType> DefineByExpression<TType>(Expression<Func<TType>> instantiatonExpression, ObjectConfiguration configuration)
        {
            var parser = ExpressionParserFactory.Create();

            parser.Parse(new InstantiationDependencyInjector(configuration, typeof(TType)), instantiatonExpression.Body);

            return new DefinitionFragment<TType>(new FragmentContext(configuration, NameFactory, ExpressionParserFactory, this));
        }

        #endregion
    }
}
